import { LngLat, LngLatBounds } from './entity/lngLat'
import { Point } from './entity/point'
import { TileLayer } from './layer/tileLayer'
import { Overlay } from './covering/overlay'
import { InfoWindow } from './covering/infoWindow'
import { Control } from './control'

/**
 * 地图的投影方式
 *
 * `EPSG:900913(墨卡托投影)`、`EPSG:4326(大地平面投影)`
 */
export type MapOptionsProjection = 'EPSG:900913' | 'EPSG:4326'

/**
 * 覆盖物容器列表
 */
export interface MapPanes {
    // TODO 有待完善
    [key: string]: unknown
}

/**
 * 地图属性对象
 */
export interface MapOptions {
    /**
     * 指定地图的投影方式，
     * 目前支持的地图投影方式有：`EPSG:900913(墨卡托投影)`、`EPSG:4326(大地平面投影)`
     *
     * 默认值为 `EPSG:900913`
     */
    projection?: MapOptionsProjection;

    /**
     * 地图允许展示的最小级别
     *
     * 默认值为 `null`
     */
    minZoom?: number;

    /**
     * 地图允许展示的最大级别
     *
     * 默认值为 `null`
     */
    maxZoom?: number;

    /**
     * 当这个选项被设置后，地图被限制在给定的地理边界内，
     * 当用户平移将地图拖动到视图以外的范围时会出现弹回的效果，
     * 并且也不允许缩小视图到给定范围以外的区域（这取决于地图的尺寸）。
     * 使用 `setMaxBounds` 方法可以动态地设置这种约束
     *
     * 默认值为 `null`
     */
    maxBounds?: LngLatBounds;

    /**
     * 地图的初始化中心点
     *
     * 默认值为 `null`
     */
    center?: LngLat;

    /**
     * 地图的初始化级别
     *
     * 默认值为 `null`
     */
    zoom?: number
}

/**
 * 地图事件工厂对象
 */
type MapEventFactory = {
    /**
     * 左键单击地图时触发此事件
     */
    click: (event: { type: 'click', target: TMap, lnglat: LngLat, containerPoint: Point }) => void;

    /**
     * 鼠标双击地图时会触发此事件
     */
    dblclick: (event: { type: 'dblclick', target: TMap, lnglat: LngLat, containerPoint: Point }) => void;

    /**
     * 右键单击地图时触发此事件
     */
    contextmenu: (event: { type: 'contextmenu', target: TMap, lnglat: LngLat, containerPoint: Point }) => void;

    /**
     * 鼠标在地图区域移动过程中触发此事件
     */
    mousemove: (event: { type: 'mousemove', target: TMap, lnglat: LngLat, containerPoint: Point }) => void;

    /**
     * 鼠标移入地图区域时触发此事件
     */
    mouseover: (event: { type: 'mouseover', target: TMap, lnglat: LngLat, containerPoint: Point }) => void;

    /**
     * 鼠标移出地图区域时触发此事件
     */
    mouseout: (event: { type: 'mouseout', target: TMap, lnglat: LngLat, containerPoint: Point }) => void;

    /**
     * 地图移动开始时触发此事件
     */
    movestart: (event: { type: 'movestart', target: TMap } ) => void;

    /**
     * 地图移动过程中触发此事件
     */
    move: (event: { type: 'move', target: TMap } ) => void;

    /**
     * 地图移动结束时触发此事件
     */
    moveend: (event: { type: 'moveend', target: TMap } ) => void;

    /**
     * 地图更改缩放级别开始时触发触发此事件
     */
    zoomstart: (event: { type: 'zoomstart', target: TMap } ) => void;

    /**
     * 地图更改缩放级别结束时触发触发此事件
     */
    zoomend: (event: { type: 'zoomend', target: TMap } ) => void;

    /**
     * 当使用 `TMap.addOverlay()` 方法向地图中添加单个覆盖物时会触发此事件
     */
    addoverlay: (event: { type: 'addoverlay', target: TMap, addoverlay: Overlay } ) => void;

    /**
     * 当使用 `TMap.removeOverlay()` 方法移除单个覆盖物时会触发此事件
     */
    removeoverlay: (event: { type: 'removeoverlay', target: TMap, removeoverlay: Overlay } ) => void;

    /**
     * 当使用 `TMap.addControl()` 方法向地图中添加单个覆盖物时会触发此事件
     */
    addcontrol: (event: { type: 'addcontrol', target: TMap, addcontrol: Control } ) => void;

    /**
     * 当使用 `TMap.removeControl()` 方法移除单个覆盖物时会触发此事件
     */
    removecontrol: (event: { type: 'removecontrol', target: TMap, removecontrol: Control } ) => void;

    /**
     * 开始拖拽地图时触发
     */
    dragstart: (event: { type: 'dragstart', target: TMap }) => void;

    /**
     * 拖拽地图过程中触发
     */
    drag: (event: { type: 'drag', target: TMap }) => void;

    /**
     * 停止拖拽地图时触发
     */
    dragend: (event: { type: 'dragend', target: TMap }) => void;

    /**
     * 当使用 `TMap.clearOverlays()` 方法一次性移除全部覆盖物时会触发此事件
     */
    clearoverlays: (event: { type: 'clearoverlays', target: TMap }) => void;

    /**
     * 添加一个自定义地图图层时触发此事件
     */
    layeradd: (event: { type: 'layeradd', target: TMap, layer: TileLayer }) => void;

    /**
     * 移除一个自定义地图图层时触发此事件
     */
    layerremove: (event: { type: 'layerremove', target: TMap, layer: TileLayer }) => void;

    /**
     * 调用 `TMap.centerAndZoom()` 方法时会触发此事件。这表示位置、缩放层级已经确定，但可能还在载入地图瓦片
     */
    load: (event: { type: 'load', target: TMap }) => void;

    /**
     * 地图可视区域大小发生变化时会触发此事件
     */
    resize: (event: { type: 'resize', target: TMap, newSize: number, oldSize: number }) => void;

    /**
     * 调用 `TMap.setMinZoom()` 和 `TMap.setMaxZoom()` 时会触发此事件
     */
    levels: (event: { type: 'levels', target: TMap, minzoom: number, maxzoom: number }) => void;

    /**
     * 触摸开始时触发此事件，仅适用移动设备
     */
    touchstart: (event: { type: 'touchstart', target: TMap, lnglat: LngLat, containerPoint: Point }) => void;

    /**
     * 触摸移动时触发此事件，仅适用移动设备
     */
    touchmove: (event: { type: 'touchmove', target: TMap, lnglat: LngLat, containerPoint: Point }) => void;

    /**
     * 触摸结束时触发此事件，仅适用移动设备
     */
    touchend: (event: { type: 'touchend', target: TMap, lnglat: LngLat, containerPoint: Point }) => void;

    /**
     * 长按事件，仅适用移动设备
     */
    longpress: (event: { type: 'longpress', target: TMap, lnglat: LngLat, containerPoint: Point }) => void;
}

/**
 * 此类是地图 `API` 的核心类，用来实例化一个地图
 */
export interface TMap {
    readonly options: MapOptions;

    /**
     * 在指定的容器内创建地图实例，
     * 之后需要调用 `TMap.centerAndZoom()` 方法对地图进行初始化。
     * 未进行初始化的地图将不能进行任何操作
     *
     * @param container 用于显示地图的地图容器，可以是一个 `HTMLDivElement` 元素或者一个 `id` 字符串
     *
     * @param opts 地图属性对象
     */
    new(container: string | HTMLElement, opts?: MapOptions): TMap;


    // 配置方法

    /**
     * 启用地图拖拽，默认启用
     */
    enableDrag: () => void;
    /**
     * 禁用地图拖拽
     */
    disableDrag: () => void;
    /**
     * 是否启用地图拖拽，`true` 表示启用，`false`表示禁止
     */
    isDrag: () => boolean;
    /**
     * 启用滚轮放大缩小，默认启用
     */
    enableScrollWheelZoom: () => void;
    /**
     * 禁用滚轮放大缩小
     */
    disableScrollWheelZoom: () => void;
    /**
     * 是否启用滚轮放大缩小，`true` 表示启用，`false`表示禁止
     */
    isScrollWheelZoom: () => boolean;
    /**
     * 启用双击放大，默认启用
     */
    enableDoubleClickZoom: () => void;
    /**
     * 禁用双击放大
     */
    disableDoubleClickZoom: () => void;
    /**
     * 是否启用双击放大，`true` 表示启用，`false`表示禁止
     */
    isDoubleClickZoom: () => boolean;
    /**
     * 启用键盘操作，默认启用
     */
    enableKeyboard: () => void;
    /**
     * 禁用键盘操作
     */
    disableKeyboard: () => void;
    /**
     * 是否启用键盘操作，`true` 表示启用，`false`表示禁止
     */
    isKeyboard: () => boolean;
    /**
     * 启用地图惯性拖拽，默认启用
     */
    enableInertia: () => void;
    /**
     * 禁用地图惯性拖拽
     */
    disableInertia: () => void;
    /**
     * 是否启用地图惯性拖拽，`true` 表示启用，`false`表示禁止
     */
    isInertia: () => boolean;
    /**
     * 启用连续缩放效果，默认启用
     */
    enableContinuousZoom: () => void;
    /**
     * 禁用连续缩放效果
     */
    disableContinuousZoom: () => void;
    /**
     * 是否启用连续缩放效果，`true` 表示启用，`false`表示禁止
     */
    isContinuousZoom: () => boolean;
    /**
     * 启用双指操作缩放，默认启用
     */
    enablePinchToZoom: () => void;
    /**
     * 禁用双指操作缩放
     */
    disablePinchToZoom: () => void;
    /**
     * 是否启用双指操作缩放，`true` 表示启用，`false`表示禁止
     */
    isPinchToZoom: () => boolean;
    /**
     * 启用自动适应容器尺寸变化，默认启用
     */
    enableAutoResize: () => void;
    /**
     * 禁用自动适应容器尺寸变化
     */
    disableAutoResize: () => void;


    // 地图状态方法

    /**
     * 返回地图投影类型信息
     */
    getCode: () => string;
    /**
     * 返回地图可视区域，以地理坐标表示
     */
    getBounds: () => LngLatBounds;
    /**
     * 返回地图当前中心点
     */
    getCenter: () => LngLat;
    /**
     * 返回地图视图的大小，以像素表示
     */
    getSize: () => Point;
    /**
     * 返回地图当前缩放级别
     */
    getZoom: () => number;
    /**
     * 返回两点之间的距离，单位是米
     *
     * @param start 起点地理坐标
     *
     * @param end 终点地理坐标
     */
    getDistance: (start: LngLat, end: LngLat) => number;
    /**
     * 根据提供的地理区域或坐标获得最佳的地图视野
     *
     * @returns 包含 `center` 和 `zoom` 属性，分别表示地图的 `中心点` 和 `当前缩放级别`
     */
    getViewport: (view:Array<LngLat>) => ({ center: LngLat, zoom: number });


    // 修改地图状态方法

    /**
     * 初始化定位地图
     *
     * @param lnglat 地图的中心点地理坐标
     *
     * @param zoom 地图的比例尺级别
     */
    centerAndZoom: (lnglat: LngLat, zoom: number) => void;

    /**
     * 将地图的中心点变换到指定的地理坐标，如果同时指定了缩放等级，则同时缩放到指定的等级
     *
     * @param lnglat 地图的中心点地理坐标
     *
     * @param zoom 地图的比例尺级别
     */
    panTo: (lnglat: LngLat, zoom?: number) => void;

    /**
     * 将地图在水平位置上移动 `x` 像素，垂直位置上移动 `y` 像素
     *
     * @param position 像素坐标
     */
    panBy: (position: Point) => void;

    /**
     * 放大一级视图
     */
    zoomIn: () => void;
    /**
     * 缩小一级视图
     */
    zoomOut: () => void;

    /**
     * 将视图缩放到指定的缩放等级，中心点坐标不变
     *
     * @param zoom 地图显示级别
     */
    setZoom: (zoom: number) => void;
    /**
     * 设置地图最小显示级别
     *
     * @param zoom 地图最小显示级别
     */
    setMinZoom: (zoom: number) => void;
    /**
     * 设置地图最大显示级别
     *
     * @param zoom 地图最大显示级别
     */
    setMaxZoom: (zoom: number) => void;

    /**
     * 通知地图其容器大小已更改。在更改了容器对象的大小后调用此方法，以便地图能够调整自己适合于新的大小
     */
    checkResize: () => void;

    /**
     * 设置地图的显示范围
     *
     * @param bounds 地图坐标范围
     */
    setMaxBounds: (bounds: LngLatBounds) => void;

    /**
     * 根据提供的坐标点数组设置地图视野，调整后的视野会保证包含提供的坐标点
     */
    setViewport: (view: Array<LngLat>) => void;

    /**
     * 设置地图样式
     *
     * @param style 地图的样式
     */
    setStyle: (style: string) => void;


    // 坐标变换

    /**
     * 将地图上相对于 `container` 的像素坐标转化为地理坐标
     */
    containerPointToLngLat: (pixel: Point) => LngLat;

    /**
     * 将地理坐标转化为地图上点的像素坐标，相对于 `container` 左上角
     */
    lngLatToContainerPoint: (lnglat: LngLat) => Point;

    /**
     * 将地图上相对于地图图层的像素坐标转化为地理坐标
     */
    layerPointToLngLat: (pixel: Point) => LngLat;

    /**
     * 将地理坐标转化为地图图层上点的像素坐标，相对于地图图层左上角
     */
    lngLatToLayerPoint: (lnglat: LngLat) => Point;


    // 覆盖物方法

    /**
     * 将覆盖物添加到地图中，一个覆盖物实例只能向地图中添加一次
     *
     * @param overlay 覆盖物对象
     */
    addOverLay: (overlay: Overlay) => void;

    /**
     * 从地图中移除覆盖物。如果覆盖物从未被添加到地图中，则该移除不起任何作用
     *
     * @param overlay 覆盖物对象
     */
    removeOverLay: (overlay: Overlay) => void;

    /**
     * 清除地图上所有覆盖物
     */
    clearOverLays: () => void;

    /**
     * 返回地图上的所有覆盖物
     */
    getOverlays: () => Array<Overlay>;

    /**
     * 返回地图覆盖物容器列表
     */
    getPanes: () => MapPanes;

    /**
     * 在地图上打开信息窗口
     *
     * @param infowin 信息窗口对象
     *
     * @param lnglat 在地图上打开的地理位置坐标
     */
    openInfoWindow: (infowin: InfoWindow, lnglat: LngLat) => void;

    /**
     * 关闭在地图上打开的信息窗口。在标注上打开的信息窗口也可通过此方法进行关闭
     */
    closeInfoWindow: () => void;


    // 控件方法

    /**
     * 将控件添加到地图，一个控件实例只能向地图中添加一次
     *
     * @param control 控件对象
     */
    addControl: (control: Control) => void;

    /**
     * 从地图中移除控件。如果控件从未被添加到地图中，则该移除不起任何作用
     *
     * @param control 控件对象
     */
    removeControl: (control: Control) => void;

    /**
     * 返回地图的容器元素。
     * 当创建用户自定义控件时，需要自行实现 `Control.initialize()` 方法，
     * 并将控件的容器元素添加到地图上，通过此方法可获得地图容器
     */
    getContainer: () => HTMLElement;


    // 地图图层方法

    /**
     * 获取所有叠加层对象，可以自己判断需要移除哪些叠加层
     */
    getLayers: () => Array<TileLayer>;

    /**
     * 给地图添加一个叠加层对象
     *
     * @param layer `TileLayer` 对象
     */
    addLayer: (layer: TileLayer) => void;

    /**
     * 移除一个叠加层对象
     *
     * @param layer `TileLayer` 对象
     */
    removeLayer: (layer: TileLayer) => void;

    /**
     * 移除所有叠加层对象
     */
    clearLayers: () => void;


    // 事件

    /**
     * 添加事件监听函数
     */
    addEventListener: <Event extends keyof MapEventFactory>(event: Event, handler: MapEventFactory[Event]) => void;

    /**
     * 移除事件监听函数
     */
    removeEventListener: <Event extends keyof MapEventFactory>(event: Event, handler: MapEventFactory[Event]) => void
}
